package jmine.tec.report.pdf;

import java.awt.Color;
import java.io.IOException;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;

import jmine.tec.report.api.Report;
import jmine.tec.report.api.ReportElement;
import jmine.tec.report.api.ReportFooter;
import jmine.tec.report.api.ReportHeader;
import jmine.tec.report.api.table.ReportCell;
import jmine.tec.report.impl.ReportBuilder;
import jmine.tec.report.impl.table.pdf.CellRendererPDFRegistry;
import jmine.tec.report.impl.table.pdf.ReportCellPDFRenderer;

import com.lowagie.text.Chunk;
import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Element;
import com.lowagie.text.Font;
import com.lowagie.text.FontFactory;
import com.lowagie.text.Image;
import com.lowagie.text.PageSize;
import com.lowagie.text.Paragraph;
import com.lowagie.text.Phrase;
import com.lowagie.text.Rectangle;
import com.lowagie.text.pdf.BaseFont;
import com.lowagie.text.pdf.PdfContentByte;
import com.lowagie.text.pdf.PdfPTable;
import com.lowagie.text.pdf.PdfPageEventHelper;
import com.lowagie.text.pdf.PdfTemplate;
import com.lowagie.text.pdf.PdfWriter;

/**
 * Implementação de exportador de relatórios para formato pdf
 * 
 * @author lundberg
 */
public class ReportPDFExporterDelegate extends PdfPageEventHelper {

    // Valores default
    private static final Rectangle DEFAULT_PAGE_SIZE = PageSize.A4; // A4

    private static final String DEFAULT_FONT_NAME = "Helvetica";

    private static final String DEFAULT_REPORT_LOGO = "reports/logo.png";

    // Magic numbers

    private static final int FONT_SIZE = 6;

    private static final int TEXT_BASE_ADJUST = 20;

    private static final int CHUNK_POSITION_ADJUST = -30;

    private static final int IMAGE_SCALE_PERCENT = 20;

    private static final float TABLE_DEFAULT_WIDTH = 100f;

    private static final int TEMPLATE_SIZE = 100;

    private static final int FONT_SIZE_DOCUMENT_TITLE = 12;

    private static final int TABLE_HEADER_SPACING_AFTER = 10;

    private static final int TABLE_HEADER_SPACING_BEFORE = 4;

    private static final int FOOTER_LINE_MARGIN = 23;

    private static final float FOOTER_LINE_WIDTH = 0.01f;

    private static final float LINE_WIDTH = 0.4f;

    private Report report;

    private ReportRendererPDFRegistry reportRendererRegistry;

    private CellRendererPDFRegistry cellRendererRegistry;

    private PdfTemplate pdfTemplate;

    private boolean paisagem = true;

    /**
     * Construtor
     * 
     * @param report report
     * @param reportRendererRegistry reportRendererRegistry
     * @param cellRendererRegistry cellRendererRegistry
     * @param paisagem boolean que diz se o pdf deve ser em paisagem
     */
    public ReportPDFExporterDelegate(Report report, ReportRendererPDFRegistry reportRendererRegistry,
            CellRendererPDFRegistry cellRendererRegistry, boolean paisagem) {
        this.report = report;
        this.reportRendererRegistry = reportRendererRegistry;
        this.cellRendererRegistry = cellRendererRegistry;
        this.paisagem = paisagem;
    }

    /**
     * Exporta o report
     * 
     * @param outputStream outputStream
     * @throws IOException IOException
     */
    public void export(OutputStream outputStream) throws IOException {
        Document document;
        Rectangle pageSize = DEFAULT_PAGE_SIZE;

        if (this.paisagem) {
            pageSize = DEFAULT_PAGE_SIZE.rotate();
        }

        document = new Document(pageSize);

        try {
            PdfWriter writer = PdfWriter.getInstance(document, outputStream);
            writer.setCloseStream(false);
            writer.setPageEvent(this);
        } catch (DocumentException e) {
            throw new IOException("Erro gerando documento PDF! " + e.getMessage());
        }

        document.open();

        this.addDocumentHeader(document, this.report.getHeader());

        for (ReportElement element : this.report.getElements()) {
            this.reportRendererRegistry.getRenderer(element.getRendererType()).render(document, element);
        }

        this.addDocumentFooter(document, this.report.getFooter());

        document.close();
    }

    /**
     * Adiciona um footer ao documento (se informado).
     * 
     * @param document document
     * @param footer {@link ReportFooter}
     * @throws IOException se ocorrer algum erro
     */
    private void addDocumentFooter(Document document, ReportFooter footer) throws IOException {
        if (footer == null) {
            return;
        }

        PdfPTable pdfTable = new PdfPTable(1);
        pdfTable.setWidthPercentage(TABLE_DEFAULT_WIDTH);

        pdfTable.getDefaultCell().setVerticalAlignment(Element.ALIGN_TOP);
        pdfTable.getDefaultCell().setNoWrap(false);
        pdfTable.getDefaultCell().setBorder(Rectangle.NO_BORDER);

        Iterator<ReportCell> it = footer.getParameters().iterator();
        while (it.hasNext()) {
            this.renderCell(pdfTable, it.next());
        }

        try {
            document.add(pdfTable);
        } catch (DocumentException e) {
            throw new IOException("Erro gerando documento PDF! " + e.getMessage());
        }
    }

    /**
     * Adiciona um header ao documento (se informado).
     * 
     * @param document document
     * @param header header
     * @throws IOException se ocorrer algum erro
     */
    private void addDocumentHeader(Document document, ReportHeader header) throws IOException {
        if (header == null || header.getParameters().isEmpty()) {
            return;
        }

        /* creating the PDF Table */
        PdfPTable outterTable = new PdfPTable(1);
        outterTable.setWidthPercentage(TABLE_DEFAULT_WIDTH);
        outterTable.setSpacingBefore(TABLE_HEADER_SPACING_BEFORE);
        outterTable.setSpacingAfter(TABLE_HEADER_SPACING_AFTER);
        outterTable.getDefaultCell().setBorder(Rectangle.BOX);

        PdfPTable pdfTable = new PdfPTable(header.getParametersPerRow());
        pdfTable.setWidthPercentage(TABLE_DEFAULT_WIDTH);

        pdfTable.getDefaultCell().setBorder(Rectangle.NO_BORDER);
        pdfTable.getDefaultCell().setVerticalAlignment(Element.ALIGN_TOP);
        pdfTable.getDefaultCell().setNoWrap(false);

        Iterator<ReportCell> it = header.getParameters().iterator();
        while (it.hasNext()) {
            this.renderCell(pdfTable, it.next());
        }

        // É necessário completar a linha para que ela seja realmente colocada na tabela.
        if (header.getParameters().size() > 0) {
            // columsLeft será sempre um valor entre 1 e getParametersPerRow (quando size for multiplo de getParametersPerRow)
            int columnsLeft = header.getParametersPerRow() - header.getParameters().size() % header.getParametersPerRow();
            if (columnsLeft != header.getParametersPerRow()) {
                pdfTable.getDefaultCell().setColspan(columnsLeft);
                pdfTable.addCell("");
            }
        }
        try {
            outterTable.addCell(pdfTable);
            document.add(outterTable);
            // document.add(pdfTable);
        } catch (DocumentException e) {
            throw new IOException("Erro gerando documento PDF! " + e.getMessage());
        }
    }

    /**
     * Metodo chamado quando o documento estiver sendo "aberto".
     * 
     * @param doc document
     * @param writer pdfWriter
     * @see com.lowagie.text.pdf.PdfPageEventHelper#onOpenDocument(com.lowagie.text.pdf.PdfWriter, com.lowagie.text.Document)
     */
    @Override
    public void onOpenDocument(PdfWriter writer, Document doc) {
        try {
            Font font = FontFactory.getFont(DEFAULT_FONT_NAME, FONT_SIZE_DOCUMENT_TITLE, Font.BOLD, Color.BLACK);

            URL imageURL = this.getClass().getClassLoader().getResource(DEFAULT_REPORT_LOGO);
            
            if (imageURL != null) {
                Image image = Image.getInstance(imageURL);
                image.scalePercent(IMAGE_SCALE_PERCENT);
                image.setAlignment(Image.LEFT | Image.TEXTWRAP);
                Chunk ck = new Chunk(image, 0, CHUNK_POSITION_ADJUST);
                Phrase p1 = new Phrase(ck);
                doc.add(p1);
                
                PdfPTable table = new PdfPTable(2);
                table.addCell(image);
                doc.add(table);
            }
            
            Paragraph para = new Paragraph(this.report.getHeader().getTitle(), font);
            para.setAlignment(Element.ALIGN_RIGHT);
            doc.add(para);
            doc.add(new Paragraph(" "));

            this.pdfTemplate = writer.getDirectContent().createTemplate(TEMPLATE_SIZE, TEMPLATE_SIZE);
        } catch (DocumentException e) {
            throw new IllegalStateException(e);
        } catch (MalformedURLException e) {
            throw new IllegalStateException(e);
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }
    }

    /**
     * Metodo chamado quando a pagina do documento PDF estiver sendo "fechada".
     * 
     * @param doc document
     * @param writer pdfWriter
     * @see com.lowagie.text.pdf.PdfPageEventHelper#onEndPage(com.lowagie.text.pdf.PdfWriter, com.lowagie.text.Document)
     */
    @Override
    public void onEndPage(PdfWriter writer, Document doc) {
        BaseFont helv;
        try {
            helv = BaseFont.createFont(DEFAULT_FONT_NAME, BaseFont.WINANSI, false);
        } catch (DocumentException e) {
            throw new IllegalStateException(e);
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }
        PdfContentByte cb = writer.getDirectContent();
        cb.saveState();

        // compose the footer
        float adjust = helv.getWidthPoint("0", FONT_SIZE);
        float textBase = doc.bottom() - TEXT_BASE_ADJUST;

        String textFooter = this.report.getParameters().getString(ReportBuilder.REPORT_FOOTER_TEXT, "");
        float textFooterSize = helv.getWidthPoint(textFooter, FONT_SIZE);
        cb.beginText();
        cb.setFontAndSize(helv, FONT_SIZE);
        cb.setTextMatrix(((doc.right() - textFooterSize) / 2) - adjust, textBase);
        cb.showText(textFooter);
        cb.endText();

        String text = "Página " + writer.getPageNumber() + " de ";
        float textSize = helv.getWidthPoint(text, FONT_SIZE);
        cb.beginText();
        cb.setFontAndSize(helv, FONT_SIZE);

        cb.setTextMatrix(doc.right() - textSize - adjust, textBase);
        cb.showText(text);
        cb.endText();

        cb.addTemplate(this.pdfTemplate, doc.right() - adjust, textBase);

        cb.beginText();
        cb.setFontAndSize(helv, FONT_SIZE);
        cb.setTextMatrix(doc.left(), textBase);
        cb.showText(new SimpleDateFormat("dd/MM/yyyy - HH:mm:ss").format(new Date()));
        cb.endText();

        cb.restoreState();

        cb.saveState();
        // draw a Rectangle around the page
        cb.setLineWidth(LINE_WIDTH);
        cb.rectangle(doc.leftMargin(), FOOTER_LINE_MARGIN, doc.getPageSize().width() - doc.leftMargin() - doc.rightMargin(),
                FOOTER_LINE_WIDTH);
        cb.stroke();
        cb.restoreState();
    }

    /**
     * Metodo chamado quando o documento PDF estiver sendo "fechado".
     * 
     * @param writer pdfWriter
     * @param doc document
     * @see com.lowagie.text.pdf.PdfPageEventHelper#onCloseDocument(com.lowagie.text.pdf.PdfWriter, com.lowagie.text.Document)
     */
    @Override
    public void onCloseDocument(PdfWriter writer, Document doc) {
        BaseFont helv;
        try {
            helv = BaseFont.createFont(DEFAULT_FONT_NAME, BaseFont.WINANSI, false);
        } catch (DocumentException e) {
            throw new IllegalStateException(e);
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }

        this.pdfTemplate.beginText();
        this.pdfTemplate.setFontAndSize(helv, FONT_SIZE);
        this.pdfTemplate.setTextMatrix(0, 0);
        this.pdfTemplate.showText("" + (writer.getPageNumber() - 1));
        this.pdfTemplate.endText();
    }

    /**
     * Cria uma célula na tabela
     * 
     * @param pdfTable pdfTable
     * @param cell cell
     */
    private void renderCell(PdfPTable pdfTable, ReportCell cell) {
        ReportCellPDFRenderer renderer = this.cellRendererRegistry.getRenderer(cell.getRendererType());
        renderer.createCell(pdfTable, cell);
    }
}
